/*
 *  Copyright (c) 2011 Leibniz Institute of Plant Genetics and Crop Plant Research (IPK), Gatersleben, Germany.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the GNU Lesser Public License v2.1
 *  which accompanies this distribution, and is available at
 *  http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 *  Contributors:
 *      Leibniz Institute of Plant Genetics and Crop Plant Research (IPK), Gatersleben, Germany - initial API and implementation
 */
package de.ipk_gatersleben.bit.bi.edal.primary_data.file.implementation;

import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.velocity.exception.VelocityException;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import de.ipk_gatersleben.bit.bi.edal.primary_data.DataManager;
import de.ipk_gatersleben.bit.bi.edal.primary_data.EdalHttpFunctions;
import de.ipk_gatersleben.bit.bi.edal.primary_data.EdalJettyHandler;
import de.ipk_gatersleben.bit.bi.edal.primary_data.EdalJettyServer;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.EdalException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PublicReference;
import de.ipk_gatersleben.bit.bi.edal.primary_data.metadata.EnumDublinCoreElements;
import de.ipk_gatersleben.bit.bi.edal.primary_data.metadata.MetaDataException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ApprovalServiceProvider;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.EdalApprovalException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.PersistentIdentifier;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.PublicationStatus;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ReviewProcess;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ReviewResult;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ReviewStatus;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ReviewStatus.ReviewStatusType;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ReviewStatus.ReviewerType;

/**
 * Implementation of {@link ApprovalServiceProvider} for the storage back end.
 * 
 * @author arendd
 */

public class ApprovalServiceProviderImplementation implements
		ApprovalServiceProvider {

	/**
	 * HashMap to store the changes PublicReference objects for the current
	 * session
	 */
	private static Map<String, PublicReferenceImplementation> synchronizedMap;

	static {
		synchronizedMap = Collections
				.synchronizedMap(new HashMap<String, PublicReferenceImplementation>());
	}

	/** {@inheritDoc} */
	@Override
	public void accept(final String ticket, int reviewerID)
			throws EdalApprovalException {
		this.changeReviewStatus(ticket, reviewerID, ReviewStatusType.ACCEPTED);

	}

	/** {@inheritDoc} */
	@Override
	public void approve(final PublicReference reference,
			final InternetAddress emailNotificationAddress)
			throws EdalApprovalException {

		if (reference instanceof PublicReferenceImplementation) {

			PublicReferenceImplementation internalRef = (PublicReferenceImplementation) reference;

			internalRef.setPublicationStatus(PublicationStatus.UNDER_REVIEW);

			ReviewResult result = ReviewProcess
					.review(new ArrayList<ReviewStatus>());

			Set<ReviewStatusImplementation> reviewSet = new HashSet<ReviewStatusImplementation>();

			if (result != null) {
				for (ReviewStatus status : result.getReviewerStatusList()) {

					Session session = ((FileSystemImplementationProvider) DataManager
							.getImplProv()).getSession();

					Transaction transaction = session.beginTransaction();

					ReviewStatusImplementation reviewStatus = new ReviewStatusImplementation();

					reviewStatus.setPublicReference(internalRef);
					reviewStatus.setEmailAddress(status.getEmailAddress()
							.getAddress());
					reviewStatus.setRequestedDate(Calendar.getInstance());
					reviewStatus.setStatusType(ReviewStatusType.UNDECIDED);
					reviewStatus.setReviewerType(status.getReviewerType());
					reviewSet.add(reviewStatus);

					session.save(reviewStatus);

					transaction.commit();
					session.close();
				}
			}

			Session session = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction = session.beginTransaction();

			internalRef.setReviewStatusSet(reviewSet);

			session.saveOrUpdate(internalRef);

			transaction.commit();
			session.close();

			String ticket = UUID.randomUUID().toString();

			this.storeTicket(ticket, reference, emailNotificationAddress);

			synchronizedMap.put(ticket, internalRef);

			try {
				for (ReviewStatus reviewStatus : result.getReviewerStatusList()) {
					this.sendRequestApprovalMail(ticket, reference,
							reviewStatus.getEmailAddress(),
							reviewStatus.getReviewerType());
				}
				this.sendStatusMailToRequestedPerson(emailNotificationAddress,
						reference);
			} catch (final EdalException e) {
				throw new EdalApprovalException(
						"unable to send requestApprovalEmails", e);
			}
		} else {
			throw new EdalApprovalException(
					"reference object is no instanceof PublicReferenceImplementation");
		}
	}

	/**
	 * Change the {@link ReviewStatus} of a {@link PublicReference} given by the
	 * ticket number.
	 * 
	 * @param ticket
	 *            to identify the {@link PublicReference}
	 * @param reviewerCode
	 *            to identify the reviewer.
	 * @param status
	 *            the new {@link ReviewStatus}.
	 * @throws EdalApprovalException
	 *             if unable to find reviewer or ticket.
	 */
	private void changeReviewStatus(final String ticket, int reviewerCode,
			ReviewStatusType status) throws EdalApprovalException {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();
		Transaction transaction = session.beginTransaction();

		ReviewersImplementation reviewerImplementation = (ReviewersImplementation) session
				.createCriteria(ReviewersImplementation.class)
				.add(Restrictions.eq("hashCode", reviewerCode)).uniqueResult();

		if (reviewerImplementation == null) {
			throw new EdalApprovalException(
					"unable to find a reviewer with this code");
		}

		TicketImplementation ticketImplementation = (TicketImplementation) session
				.createCriteria(TicketImplementation.class)
				.add(Restrictions.eq("ticket", ticket)).uniqueResult();

		if (ticketImplementation == null) {
			throw new EdalApprovalException(
					"unable to find a ticket with this code");
		}

		@SuppressWarnings("unchecked")
		List<ReviewStatusImplementation> reviewStatusList = (List<ReviewStatusImplementation>) session
				.createCriteria(ReviewStatusImplementation.class)
				.add(Restrictions.eq("emailAddress",
						reviewerImplementation.getEmailAddress()))
				.add(Restrictions.eq("publicReference",
						ticketImplementation.getReference())).list();

		for (ReviewStatusImplementation reviewStatusImplementation : reviewStatusList) {
			reviewStatusImplementation.setStatusType(status);
			session.saveOrUpdate(reviewStatusImplementation);
		}

		transaction.commit();
		session.close();
	}

	/** {@inheritDoc} */
	@Override
	public void checkOpenReviewProcesses(
			Map<PublicReference, List<ReviewStatus>> results)
			throws EdalApprovalException {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		for (Entry<PublicReference, List<ReviewStatus>> result : results
				.entrySet()) {

			TicketImplementation ticket = (TicketImplementation) session
					.createCriteria(TicketImplementation.class)
					.add(Restrictions.eq("reference", result.getKey()))
					.uniqueResult();

			DataManager
					.getImplProv()
					.getLogger()
					.debug("CheckOpenReviewProcess for Ticket : " + "\t"
							+ ticket.getTicket());

			ReviewResult reviewResult = null;
			try {
				reviewResult = ReviewProcess.review(result.getValue());
			} catch (EdalApprovalException e) {
				throw new EdalApprovalException(
						"unable to check open review processes: "
								+ e.getMessage(), e.getCause());
			}

			try {
				updateReviewStatus(result.getKey(), reviewResult);
			} catch (EdalApprovalException e) {
				throw new EdalApprovalException(
						"unable to check open review processes: "
								+ e.getMessage(), e.getCause());
			}
		}

		session.close();

	}

	/**
	 * Create the landing page string without the server part (host and port)
	 * for a given PublicReference.
	 * 
	 * @param reference
	 *            the {@link PublicReference} corresponding to this landing
	 *            page.
	 * @return the landing page
	 */
	private String createLandingPageString(PublicReference reference) {

		String landingpage = EdalJettyServer.EDAL_PATH_SEPARATOR
				+ reference.getIdentifierType().toString()
				+ EdalJettyServer.EDAL_PATH_SEPARATOR
				+ reference.getInternalID()
				+ EdalJettyServer.EDAL_PATH_SEPARATOR
				+ reference.getVersion().getEntity().getID()
				+ EdalJettyServer.EDAL_PATH_SEPARATOR
				+ reference.getVersion().getRevision();

		return landingpage;
	}

	/**
	 * Create the complete landing page {@link URL} including server part to
	 * send it in an email to the requested author.
	 * 
	 * @param reference
	 *            the {@link PublicReference} corresponding to this {@link URL}.
	 * @return the complete URL
	 * @throws EdalApprovalException
	 *             if unable to create the {@link URL}.
	 */
	private URL createLandingPageURL(PublicReferenceImplementation reference)
			throws EdalApprovalException {
		URL url = null;
		try {
			url = EdalJettyServer.getServerURL();
			return new URL(url, createLandingPageString(reference));

		} catch (EdalException | MalformedURLException e) {
			throw new EdalApprovalException(
					"unable to create URL for the landing page : "
							+ e.getMessage(), e);
		}
	}

	/**
	 * Delete all existing {@link ReviewStatusImplementation} after a
	 * {@link PublicReference} was accepted or rejected.
	 * 
	 * @param reference
	 *            the {@link PublicReference} to delete all
	 *            {@link ReviewStatusImplementation}.
	 */
	private void deleteReviewStatus(PublicReferenceImplementation reference) {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		Transaction transaction = session.beginTransaction();

		@SuppressWarnings("unchecked")
		List<ReviewStatusImplementation> reviewStatus = session
				.createCriteria(ReviewStatusImplementation.class)
				.add(Restrictions.eq("publicReference", reference)).list();

		for (ReviewStatusImplementation reviewStatusImplementation : reviewStatus) {
			session.delete(reviewStatusImplementation);
		}

		transaction.commit();
		session.close();

	}

	/**
	 * Delete the ticket in the database, after it was accepted or rejected.
	 * 
	 * @param ticket
	 *            the {@link TicketImplementation} to delete.
	 */
	private void deleteTicket(final String ticket) {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		Transaction transaction = session.beginTransaction();

		TicketImplementation ticketImplementation = (TicketImplementation) session
				.createCriteria(TicketImplementation.class)
				.add(Restrictions.eq("ticket", ticket)).uniqueResult();

		session.delete(ticketImplementation);

		transaction.commit();
		session.close();
		
		EdalJettyHandler.deleteTicketFromHashMap(ticket);

	}

	/** {@inheritDoc} */
	@Override
	public Map<PublicReference, List<ReviewStatus>> getAllOpenReviewProcesses() {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		@SuppressWarnings("unchecked")
		List<PublicReferenceImplementation> publicReferences = (List<PublicReferenceImplementation>) session
				.createCriteria(PublicReferenceImplementation.class)
				.add(Restrictions.isNull("acceptedDate"))
				.add(Restrictions.isNull("rejectedDate"))
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();

		DataManager.getImplProv().getLogger()
				.debug("open PublicReferences : " + publicReferences.size());

		session.close();

		Map<PublicReference, List<ReviewStatus>> openReviewProcesses = new HashMap<PublicReference, List<ReviewStatus>>(
				publicReferences.size());

		for (PublicReferenceImplementation publicReference : publicReferences) {

			Set<ReviewStatusImplementation> privateSet = publicReference
					.getReviewStatusSet();

			DataManager.getImplProv().getLogger()
					.debug("open ReviewStatus : " + privateSet.size());

			if (!privateSet.isEmpty()) {
				List<ReviewStatus> publicSet = new ArrayList<ReviewStatus>(
						privateSet.size());

				for (ReviewStatusImplementation reviewStatusImplementation : privateSet) {
					publicSet.add(reviewStatusImplementation.toReviewStatus());

				}
				openReviewProcesses.put(publicReference, publicSet);
			}
		}

		DataManager.getImplProv().getLogger()
				.debug("open ReviewProcesses : " + openReviewProcesses.size());

		return openReviewProcesses;
	}

	/**
	 * Load the eMail address of the user, that requested a ticket to approve a
	 * {@link PublicReference}.
	 * 
	 * @param ticket
	 *            the find the corresponding eMail address
	 * @return the eMail Address
	 */
	private String getEmailNotificationAddress(String ticket) {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		Transaction transaction = session.beginTransaction();

		Criteria criteria = session.createCriteria(TicketImplementation.class)
				.add(Restrictions.eq("ticket", ticket));

		TicketImplementation ticketImplementation = (TicketImplementation) criteria
				.uniqueResult();

		transaction.commit();

		session.close();

		return ticketImplementation.getEmailNotificationAddress();

	}

	@Override
	public String storeNewDOI(PublicReference reference, String doi, int year)
			throws EdalApprovalException {

		if (reference instanceof PublicReferenceImplementation) {

			PublicReferenceImplementation publicReferenceImplementation = (PublicReferenceImplementation) reference;
			publicReferenceImplementation.setVersion(reference.getVersion());
			publicReferenceImplementation.setAcceptedDate(Calendar
					.getInstance());
			publicReferenceImplementation.setAssignedID(doi);
			publicReferenceImplementation
					.setPublicationStatus(PublicationStatus.ACCEPTED);
			publicReferenceImplementation.setPublic(true);
			publicReferenceImplementation.setIdentifierType(reference
					.getIdentifierType());

			DoiImplementation doiImplementation = new DoiImplementation();

			doiImplementation.setReference(publicReferenceImplementation);
			doiImplementation.setUrl(doi);
			doiImplementation.setYear(year);

			try {

				Session session = ((FileSystemImplementationProvider) DataManager
						.getImplProv()).getSession();

				Transaction transaction = session.beginTransaction();

				session.saveOrUpdate(publicReferenceImplementation);

				transaction.commit();
				session.close();

				Session session2 = ((FileSystemImplementationProvider) DataManager
						.getImplProv()).getSession();

				Transaction transaction2 = session2.beginTransaction();

				session2.save(doiImplementation);

				transaction2.commit();
				session2.close();
			} catch (Exception e) {
				e.printStackTrace();
			}

			return doi;
		} else {
			throw new EdalApprovalException(
					"reference object is no instanceof PublicReferenceImplementation");
		}
	}

	@Override
	public String getNewURL(PublicReference reference)
			throws EdalApprovalException {

		String url = "";

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		Transaction transaction = session.beginTransaction();

		int year = Calendar.getInstance().get(Calendar.YEAR);

		int numberOfStoredIds = session.createCriteria(UrlImplementation.class)
				.add(Restrictions.eq("year", year)).list().size();

		try {

			url = EdalJettyServer.getServerURL()
					+ EdalJettyServer.EDAL_PATH_SEPARATOR
					+ year
					+ EdalJettyServer.EDAL_PATH_SEPARATOR
					+ numberOfStoredIds
					+ EdalJettyServer.EDAL_PATH_SEPARATOR
					+ reference.getVersion().getMetaData()
							.getElementValue(EnumDublinCoreElements.TITLE)
							.toString();

		} catch (EdalException | MetaDataException e) {
			e.printStackTrace();
		}

		PublicReferenceImplementation publicReferenceImplementation = (PublicReferenceImplementation) reference;
		publicReferenceImplementation.setVersion(reference.getVersion());
		publicReferenceImplementation.setAcceptedDate(Calendar.getInstance());
		publicReferenceImplementation.setAssignedID(url);
		publicReferenceImplementation
				.setPublicationStatus(PublicationStatus.ACCEPTED);
		publicReferenceImplementation.setPublic(true);
		publicReferenceImplementation
				.setIdentifierType(PersistentIdentifier.URL);

		UrlImplementation urlImplementation = new UrlImplementation();

		urlImplementation.setReference(publicReferenceImplementation);
		urlImplementation.setUrl(url);
		urlImplementation.setYear(year);

		session.update(publicReferenceImplementation);
		session.save(urlImplementation);

		transaction.commit();

		session.close();

		return url;
	}

	@Override
	public PublicReference getPublicReferenceByInternalId(String internalId)
			throws EdalException {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		PublicReferenceImplementation publicReference = (PublicReferenceImplementation) session
				.createCriteria(PublicReferenceImplementation.class)
				.add(Restrictions.eq("internalID", internalId)).uniqueResult();

		session.close();

		if (publicReference != null) {
			return publicReference;
		} else {
			throw new EdalException("no PublicReference with ID '" + internalId
					+ "' found !");
		}
	}

	/** {@inheritDoc} */
	@Override
	public void reject(final String ticket, int reviewerID)
			throws EdalApprovalException {

		this.changeReviewStatus(ticket, reviewerID, ReviewStatusType.REJECTED);

	}

	/**
	 * Send an eMail to the user that his request was accepted.
	 * 
	 * @param newId
	 *            the new assigned ID
	 * @param emailAddress
	 *            the eMail address of the user.
	 * @param landingPage
	 *            the link to the eDAL landingPage.
	 * @throws EdalApprovalException
	 *             if unable to generate the eMail.
	 */
	private void sendAcceptedMail(final String newId, String emailAddress,
			URL landingPage, PublicReference publicReference)
			throws EdalApprovalException {

		VeloCityGenerator veloCityGenerator = new VeloCityGenerator();

		String message;
		try {
			message = veloCityGenerator.generateAcceptedEmail(newId,
					landingPage, publicReference).toString();
		} catch (VelocityException e) {
			throw new EdalApprovalException("unable to generate e-mail", e);
		}

		this.sendEmail(message, "eDAL [Your PublicReference was accepted]",
				emailAddress);

	}

	/**
	 * Function to send an eMail to the given recipient.
	 * 
	 * @param emailAddress
	 *            the eMail address of the recipient.
	 */
	private void sendEmail(final String message, final String subject,
			final String emailAddress) {

		javax.mail.Session session = null;

		InternetAddress addressFrom = null;

		Properties properties = new Properties();

		properties.put("mail.smtp.host", DataManager.getConfiguration()
				.getMailSmtpHost());

		if (DataManager.getConfiguration().getMailSmtpLogin() == null
				|| DataManager.getConfiguration().getMailSmtpLogin().isEmpty()) {

			session = javax.mail.Session.getDefaultInstance(properties);

			try {
				addressFrom = new InternetAddress(DataManager
						.getConfiguration().getEdalEmailAddress(),
						"eDAL-Service <"
								+ DataManager.getConfiguration()
										.getEdalEmailAddress() + ">");
			} catch (UnsupportedEncodingException e) {
				DataManager.getConfiguration().getErrorLogger()
						.fatal(emailAddress + " : " + e.getMessage());
			}

		} else {

			properties.put("mail.smtp.auth", "true");

			Authenticator authenticator = new Authenticator() {
				private PasswordAuthentication authentication;

				{
					authentication = new PasswordAuthentication(DataManager
							.getConfiguration().getMailSmtpLogin(), DataManager
							.getConfiguration().getMailSmtpPassword());
				}

				protected PasswordAuthentication getPasswordAuthentication() {
					return authentication;
				}
			};

			session = javax.mail.Session.getInstance(properties, authenticator);

			try {
				addressFrom = new InternetAddress(DataManager
						.getConfiguration().getMailSmtpLogin(),
						"eDAL-Service <"
								+ DataManager.getConfiguration()
										.getEdalEmailAddress() + ">");
			} catch (UnsupportedEncodingException e) {
				DataManager.getConfiguration().getErrorLogger()
						.fatal(emailAddress + " : " + e.getMessage());
			}

		}

		final Message mail = new MimeMessage(session);
		try {

			mail.setFrom(addressFrom);
			final InternetAddress addressTo = new InternetAddress(emailAddress);
			mail.setRecipient(Message.RecipientType.TO, addressTo);
			mail.setSubject(subject);
			mail.setContent(message, "text/html; charset=UTF-8");

			Transport.send(mail);

		} catch (MessagingException e) {
			DataManager.getConfiguration().getErrorLogger()
					.fatal(emailAddress + " : " + e.getMessage());
		}
	}

	/**
	 * Send an eMail to the user that his request was rejected.
	 * 
	 * @param emailAddress
	 *            the eMail address of the user.
	 * @throws EdalApprovalException
	 *             if unable to generate the eMail.
	 */
	private void sendRejectedMails(String emailAddress,
			PublicReference publicReference) throws EdalApprovalException {

		VeloCityGenerator veloCityGenerator = new VeloCityGenerator();

		String message;
		try {
			message = veloCityGenerator.generateRejectedEmail(publicReference)
					.toString();
		} catch (VelocityException e) {
			throw new EdalApprovalException("unable to generate e-mail", e);
		}

		this.sendEmail(message, "eDAL [Your PublicReference was rejected]",
				emailAddress);

	}

	/**
	 * Send all reviewers a Request-eMail.
	 * 
	 * @throws EdalApprovalException
	 *             if unable to generate the eMails
	 */
	private void sendRequestApprovalMail(String ticket,
			PublicReference reference, InternetAddress emailAddress,
			ReviewerType reviewerType) throws EdalApprovalException {

		DataManager.getImplProv().getLogger()
				.debug("Send Requestmail :" + emailAddress);

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		int hashCode = emailAddress.getAddress().hashCode();

		ReviewersImplementation reviewer = (ReviewersImplementation) session
				.createCriteria(ReviewersImplementation.class)
				.add(Restrictions.eq("hashCode", hashCode))
				.add(Restrictions.eq("emailAddress", emailAddress.getAddress()))
				.uniqueResult();

		if (reviewer == null) {

			reviewer = new ReviewersImplementation(emailAddress.getAddress(),
					hashCode);
			Transaction transaction = session.beginTransaction();
			session.save(reviewer);
			transaction.commit();
		}

		session.close();

		final VeloCityGenerator veloCityGenerator = new VeloCityGenerator();
		try {
			final StringWriter stringWriter = veloCityGenerator
					.generateRequestEmail(
							reference.getVersion(),
							EdalJettyServer.generateMethodURL(ticket,
									emailAddress.hashCode(),
									EdalHttpFunctions.ACCEPT),
							EdalJettyServer.generateMethodURL(ticket,
									emailAddress.hashCode(),
									EdalHttpFunctions.REJECT),
							EdalJettyServer.generateMethodURL(ticket,
									emailAddress.hashCode(),
									EdalHttpFunctions.REVIEW),
							reference.getRequestedPrincipal(),
							emailAddress,
							reviewerType,
							EdalJettyServer.generateReviewerURL(
									this.createLandingPageURL((PublicReferenceImplementation) reference),
									hashCode));
			this.sendEmail(stringWriter.toString(), "eDAL [Please approve "
					+ reference.getIdentifierType() + "]",
					emailAddress.getAddress());
		} catch (final VelocityException | EdalException e) {
			throw new EdalApprovalException("unable to generate e-mail", e);
		}

	}

	/**
	 * Send a status eMail to a requesting person.
	 * 
	 * @param emailAddress
	 *            the eMail address of the person who requested the
	 *            {@link PublicReference} to send the eMail.
	 * @param publicReference
	 *            the {@link PublicReference}
	 * @throws EdalApprovalException
	 *             if unable to generate the eMail.
	 */
	private void sendStatusMailToRequestedPerson(
			final InternetAddress emailAddress, PublicReference publicReference)
			throws EdalApprovalException {

		try {
			StringWriter stringWriter = new VeloCityGenerator()
					.generateStatusEmail(publicReference);
			this.sendEmail(stringWriter.toString(),
					"eDAL [Your requesting status]", emailAddress.getAddress());
		} catch (VelocityException e) {
			throw new EdalApprovalException("unable to generate e-mail", e);
		}

	}

	/**
	 * Update the isPublic flag of the {@link PublicReferenceImplementation} to
	 * the given ticket to false;
	 * 
	 * @param ticket
	 *            the ticket for the corresponding
	 *            {@link PublicReferenceImplementation}.
	 * @throws EdalApprovalException
	 *             if unable to set the {@link PublicReference} to false.
	 */
	private void setPublicReferenceToFalse(String ticket)
			throws EdalApprovalException {
		PublicReferenceImplementation publicRef = synchronizedMap.get(ticket);

		if (publicRef == null) {

			Session session = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction = session.beginTransaction();

			TicketImplementation ticketImplementation = (TicketImplementation) session
					.createCriteria(TicketImplementation.class)
					.add(Restrictions.eq("ticket", ticket)).uniqueResult();

			PublicReferenceImplementation publicReference = ticketImplementation
					.getReference();

			transaction.commit();
			session.close();

			try {
				publicReference.getReferencable().rejectApprovalRequest(
						publicReference);
			} catch (EdalException e) {
				throw new EdalApprovalException(
						"unable to reject PublicReference", e);
			}

			Session session2 = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction2 = session2.beginTransaction();

			publicReference.setRejectedDate(Calendar.getInstance());
			publicReference.setPublicationStatus(PublicationStatus.REJECTED);
			session2.update(publicReference);

			transaction2.commit();
			session2.close();

		} else {

			try {
				publicRef.getReferencable().rejectApprovalRequest(publicRef);
			} catch (EdalException e) {
				throw new EdalApprovalException(
						"unable to reject PublicReference", e);
			}

			Session session = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction = session.beginTransaction();

			publicRef.setRejectedDate(Calendar.getInstance());
			publicRef.setPublicationStatus(PublicationStatus.REJECTED);
			session.update(publicRef);
			transaction.commit();
			session.close();

		}

	}

	/**
	 * Update the isPublic flag of the {@link PublicReferenceImplementation} to
	 * the given ticket to true; Store the new ID to the database;
	 * 
	 * @param ticket
	 *            the ticket for the corresponding
	 *            {@link PublicReferenceImplementation}.
	 * @return the new registered ID.
	 * @throws EdalApprovalException
	 *             if unable to get a new ID.
	 */
	private String setPublicReferenceToTrue(final String ticket)
			throws EdalApprovalException {

		PublicReferenceImplementation publicRef = synchronizedMap.get(ticket);

		if (publicRef == null) {
			Session session = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction = session.beginTransaction();

			TicketImplementation ticketImplementation = (TicketImplementation) session
					.createCriteria(TicketImplementation.class)
					.add(Restrictions.eq("ticket", ticket)).uniqueResult();

			PublicReferenceImplementation publicReference = ticketImplementation
					.getReference();

			transaction.commit();
			session.close();

			String newId = "";
			try {

				newId = publicReference.getReferencable()
						.acceptApprovalRequest(publicReference);
			} catch (EdalException e) {
				throw new EdalApprovalException("unable to get new ID: "
						+ e.getMessage(), e.getCause());
			}

			Session session2 = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction2 = session2.beginTransaction();

			publicReference.setAssignedID(newId);
			publicReference.setPublic(true);
			publicReference.setAcceptedDate(Calendar.getInstance());
			publicReference.setPublicationStatus(PublicationStatus.ACCEPTED);
			session2.update(publicReference);

			transaction2.commit();
			session2.close();

			return newId;
		} else {

			String newId = "";
			try {
				newId = publicRef.getReferencable().acceptApprovalRequest(
						publicRef);
			} catch (EdalException e) {
				throw new EdalApprovalException("unable to get new ID: "
						+ e.getMessage(), e.getCause());
			}

			publicRef.setAssignedID(newId);
			publicRef.setPublic(true);
			publicRef.setAcceptedDate(Calendar.getInstance());
			publicRef.setPublicationStatus(PublicationStatus.ACCEPTED);

			Session session = ((FileSystemImplementationProvider) DataManager
					.getImplProv()).getSession();

			Transaction transaction = session.beginTransaction();

			session.update(publicRef);

			transaction.commit();
			session.close();

			return newId;
		}
	}

	/**
	 * Store an new {@link TicketImplementation} into the database.
	 * 
	 * @param ticket
	 *            the ticket to store into the database.
	 * @param reference
	 *            the corresponding {@link PublicReference}
	 * @param emailNotificationAddress
	 *            the eMail address of the requesting user
	 */
	private void storeTicket(String ticket, PublicReference reference,
			InternetAddress emailNotificationAddress) {

		PublicReferenceImplementation publicReference = (PublicReferenceImplementation) reference;

		publicReference.setRequestedDate(Calendar.getInstance());

		publicReference.setLandingPage(createLandingPageString(reference));

		TicketImplementation ticketImplementation = new TicketImplementation(
				ticket, publicReference, emailNotificationAddress.getAddress());

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		Transaction transaction = session.beginTransaction();
		session.update(publicReference);
		session.save(ticketImplementation);
		transaction.commit();
		session.close();

	}

	/**
	 * Update the review Status of the given {@link PublicReference}.
	 * 
	 * @param publicReference
	 * @param result
	 * @throws EdalApprovalException
	 */
	private void updateReviewStatus(PublicReference publicReference,
			ReviewResult result) throws EdalApprovalException {

		Session session = ((FileSystemImplementationProvider) DataManager
				.getImplProv()).getSession();

		TicketImplementation ticket = (TicketImplementation) session
				.createCriteria(TicketImplementation.class)
				.add(Restrictions.eq("reference", publicReference))
				.uniqueResult();

		session.close();

		switch (result.getReviewStatusType()) {

		case ACCEPTED:

			// System.out.println("Reference was accepted !!!");

			String newId = "";
			try {
				newId = this.setPublicReferenceToTrue(ticket.getTicket());
			} catch (final EdalException e) {
				throw new EdalApprovalException(
						"unable to set PublicReference to true: "
								+ e.getMessage(), e.getCause());
			}
			String emailAdress = ticket.getEmailNotificationAddress();

			this.deleteReviewStatus(ticket.getReference());
			this.deleteTicket(ticket.getTicket());

			try {
				this.sendAcceptedMail(newId, emailAdress,
						createLandingPageURL(ticket.getReference()),
						publicReference);
			} catch (final EdalApprovalException e) {
				throw new EdalApprovalException("unable to send AcceptedEmail",
						e);
			}

			synchronizedMap.remove(ticket);

			break;

		case REJECTED:

			// System.out.println("Reference was rejected !!!");

			try {
				setPublicReferenceToFalse(ticket.getTicket());
			} catch (EdalApprovalException e) {
				throw new EdalApprovalException(
						"unable to set PublicReference to false", e);
			}

			try {
				this.sendRejectedMails(
						this.getEmailNotificationAddress(ticket.getTicket()),
						publicReference);
			} catch (final EdalApprovalException e) {
				throw new EdalApprovalException("unable to send RejectedEmail",
						e);
			}

			this.deleteReviewStatus(ticket.getReference());

			this.deleteTicket(ticket.getTicket());

			synchronizedMap.remove(ticket.getTicket());

			break;

		case UNDECIDED:

			// System.out.println("Reference is still undecided !!!");

			for (ReviewStatus reviewStatus : result.getReviewerStatusList()) {

				try {
					sendRequestApprovalMail(ticket.getTicket(),
							ticket.getReference(),
							reviewStatus.getEmailAddress(),
							reviewStatus.getReviewerType());
				} catch (EdalApprovalException e) {
					throw new EdalApprovalException(
							"unable to send RequestEmail", e);
				}
			}

			break;

		default:
			break;
		}

	}

}